﻿using FileTransSystem.Helper;
using FileTransSystem.Model;
using FileTransSystem.ViewModel;
using MaterialDesignThemes.Wpf;
using NetworkCommsDotNet;
using NetworkCommsDotNet.Connections;
using NetworkCommsDotNet.Connections.TCP;
using NetworkCommsDotNet.Tools;
using Panuon.UI.Silver;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using Clipboard = System.Windows.Clipboard;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;

namespace FileTransSystem
{
    /// <summary>
    /// FileManager.xaml 的交互逻辑
    /// </summary>
    public partial class FileManager : WindowX
    {
        /// <summary>
        /// Boolean used for suppressing errors during GUI close
        /// </summary>
        static volatile bool windowClosing = false;

        /// <summary>
        /// Custom sendReceiveOptions used for sending files. Can be changed via GUI.
        /// </summary>
        SendReceiveOptions customOptions = new SendReceiveOptions<ProtobufSerializer>();

        SQLiteConnection _sqlConnection;

        UserModel _userModel;

        public FileManager(UserModel userModel)
        {
            InitializeComponent();
            _userModel = userModel;
            DataContext = InitUserFile();
            ButtonShareDownload.DataContext = new DialogsViewModel(DataGridFile, _userModel);

        }

        private ObservableCollection<SelectableViewModel> InitUserFile()
        {
            ObservableCollection<SelectableViewModel> userFileObservableCollection =
                new ObservableCollection<SelectableViewModel>();
            _sqlConnection = GetSqlConnection.Connect();
            DataSet fileSet =
                DatabaseHelper.ExecuteDataSet(_sqlConnection, "select * from fileInfo where ownedBy=@ownedBy",
                    new object[1] {_userModel._account});
            DataTable table = fileSet.Tables[0];
            for (int i = 0; i < table.Rows.Count; i++)
            {
                userFileObservableCollection.Add(new SelectableViewModel
                {
                    FileName = table.Rows[i]["fileName"].ToString(),
                    FileSize = table.Rows[i]["fileLength"].ToString(),
                    UploadTime = table.Rows[i]["uploadTime"].ToString(),
                    FileMd5 = table.Rows[i]["fileMD5"].ToString(),
                    UploadUser = table.Rows[i]["uploadUser"].ToString(),
                    OwnedBy = table.Rows[i]["ownedBy"].ToString(),
                    FilePath = table.Rows[i]["filePath"].ToString()
                });
            }

            return userFileObservableCollection;
        }

        private void ButtonUpload_OnClick(object sender, RoutedEventArgs e)
        {
            //Create an OpenFileDialog so that we can request the file to send
            OpenFileDialog openDialog = new OpenFileDialog();
            openDialog.Title = "选择上传的文件";
            openDialog.Multiselect = false;

            //If a file was selected
            if (openDialog.ShowDialog() == true)
            {
                //Disable the send and compression buttons
                ButtonUpload.IsEnabled = false;
                CheckBoxIsCompress.IsEnabled = false;

                //Parse the necessary remote information
                string filename = openDialog.FileName;
                var fileInfo = new FileInfo(filename);

                string remoteIP = this.TextBoxRemoteIP.Text;
                string fileMD5 = FileMD5Helper.GetMD5HashFromFile(fileInfo.FullName);
                string remotePort = this.TextBoxRemotePort.Text;

                //Set the send progress bar to 0
                UpdateSendProgress(0);

                //Perform the send in a task so that we don't lock the GUI
                Task.Factory.StartNew(() =>
                {
                    FileStream stream = null;
                    try
                    {
                        //Create a fileStream from the selected file
                        stream = new FileStream(filename, FileMode.Open, FileAccess.Read);

                        //Wrap the fileStream in a threadSafeStream so that future operations are thread safe
                        StreamTools.ThreadSafeStream safeStream = new StreamTools.ThreadSafeStream(stream);

                        //Get the filename without the associated path information
                        string shortFileName = System.IO.Path.GetFileName(filename);

                        //Parse the remote connectionInfo
                        //We have this in a separate try catch so that we can write a clear message to the log window
                        //if there are problems
                        ConnectionInfo remoteInfo;
                        try
                        {
                            remoteInfo = new ConnectionInfo(remoteIP, int.Parse(remotePort));
                        }
                        catch (Exception)
                        {
                            throw new InvalidDataException("Failed to parse remote IP and port. Check and try again.");
                        }

                        //Get a connection to the remote side
                        Connection connection = TCPConnection.GetConnection(remoteInfo);

                        //Break the send into 20 segments. The less segments the less overhead 
                        //but we still want the progress bar to update in sensible steps
                        long sendChunkSizeBytes = (long) (stream.Length / 20.0) + 1;

                        //Limit send chunk size to 500MB
                        long maxChunkSizeBytes = 500L * 1024L * 1024L;
                        if (sendChunkSizeBytes > maxChunkSizeBytes) sendChunkSizeBytes = maxChunkSizeBytes;

                        long totalBytesSent = 0;
                        do
                        {
                            //Check the number of bytes to send as the last one may be smaller
                            long bytesToSend = (totalBytesSent + sendChunkSizeBytes < stream.Length
                                ? sendChunkSizeBytes
                                : stream.Length - totalBytesSent);

                            //Wrap the threadSafeStream in a StreamSendWrapper so that we can get NetworkComms.Net
                            //to only send part of the stream.
                            StreamTools.StreamSendWrapper streamWrapper =
                                new StreamTools.StreamSendWrapper(safeStream, totalBytesSent, bytesToSend);

                            //We want to record the packetSequenceNumber
                            long packetSequenceNumber;
                            //Send the select data
                            connection.SendObject("PartialFileData", streamWrapper, customOptions,
                                out packetSequenceNumber);
                            //Send the associated SendInfo for this send so that the remote can correctly rebuild the data
                            connection.SendObject("PartialFileDataInfo",
                                new SendInfo(shortFileName, stream.Length, totalBytesSent, packetSequenceNumber),
                                customOptions);

                            totalBytesSent += bytesToSend;

                            //Update the GUI with our send progress
                            UpdateSendProgress((double) totalBytesSent / stream.Length);
                        } while (totalBytesSent < stream.Length);

                        //Clean up any unused memory
                        GC.Collect();

                        AddLineToLog("文件上传完成 '" + connection.ConnectionInfo.ToString() + "'.");
                        SyncWithDatabase(fileInfo, fileMD5);
                        //完成上传，写入数据库信息
                    }
                    catch (CommunicationException)
                    {
                        //If there is a communication exception then we just write a connection
                        //closed message to the log window
                        AddLineToLog("Failed to complete send as connection was closed.");
                    }
                    catch (Exception ex)
                    {
                        //If we get any other exception which is not an InvalidDataException
                        //we log the error
                        if (!windowClosing && ex.GetType() != typeof(InvalidDataException))
                        {
                            AddLineToLog(ex.Message.ToString());
                            LogTools.LogException(ex, "SendFileError");
                        }
                    }
                    finally
                    {
                        stream?.Close();
                    }

                    //Once the send is finished reset the send progress bar
                    //UpdateSendProgress(0);

                    //Once complete enable the send button again
                    ButtonUpload.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        ButtonUpload.IsEnabled = true;
                        CheckBoxIsCompress.IsEnabled = true;
                        RefreshDataGridHelper.Refresh(
                            (ObservableCollection<SelectableViewModel>) (DataGridFile.DataContext),
                            "select * from fileInfo where ownedBy=@ownedBy order by fileName",
                            new Object[1] {_userModel._account});
                    }));
                });
            }
        }

        /// <summary>
        /// Updates the send file progress bar
        /// </summary>
        /// <param name="percentComplete"></param>
        private void UpdateSendProgress(double percentComplete)
        {
            //Use dispatcher incase method is not called from GUI thread
            // sendProgress.Dispatcher.BeginInvoke(new Action(() =>
            // {
            //     sendProgress.Value = percentComplete;
            // }));
        }

        private void AddLineToLog(string logLine)
        {
            //Use dispatcher incase method is not called from GUI thread
            TextBlockMessage.Dispatcher.BeginInvoke(new Action(() =>
            {
                TextBlockMessage.Text += DateTime.Now.ToShortTimeString() + " - " + logLine + "\n";
                //Update the scroller so that we are always at the bottom
                ScrollViewerMessage.ScrollToBottom();
            }));
        }


        //传入之前计算的md5，避免文件被占用抛出异常
        private void SyncWithDatabase(FileInfo fileInfo, string fileMD5)
        {
            string time = DateTime.Now.ToLocalTime().ToString();
            string fileLength = FileSizeHelper.GetFileSize(fileInfo.Length);
            string fileName = fileInfo.Name;
            _sqlConnection = GetSqlConnection.Connect();
            //检查数据库中是否有文件的md5相同
            DataSet fileDataSet = DatabaseHelper.ExecuteDataSet(_sqlConnection,
                "select * from fileInfo where fileMD5=@fileMd5",
                new object[1] {fileMD5});
            DataTable table = fileDataSet.Tables[0];
            //有重复的文件就更新上传信息，没有就直接插入
            if (table.Rows.Count == 0)
            {
                Object[] paramList = new object[7]
                {
                    fileName, fileLength, fileMD5, time, _userModel._account, _userModel._account,
                    Directory.GetCurrentDirectory() + "\\" + fileName
                };
                DatabaseHelper.ExecuteNonQuery(_sqlConnection,
                    "insert into fileInfo (fileName, fileLength, fileMD5, uploadTime, uploadUser, ownedBy,filePath) values(@fileName,@fileLength,@fileMD5,@time,@uploadUser,@ownedBy,@filePath)",
                    paramList);
            }
            else
            {
                Object[] paramList = new object[6]
                {
                    fileName, time, _userModel._account, _userModel._account, fileMD5,
                    Directory.GetCurrentDirectory() + "\\" + fileName
                };
                DatabaseHelper.ExecuteNonQuery(_sqlConnection,
                    "update fileInfo set fileName=@fileName,uploadTime=@time,uploadUser=@uploadUser,ownedBy=@ownedBy,filePath=@filePath where fileMD5=@fileMd5",
                    paramList);
            }
        }

        private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (!(e.Column.Header.ToString().Equals("FileName") || e.Column.Header.Equals("FileSize") ||
                  e.Column.Header.ToString().Equals("UploadTime") || e.Column.Header.Equals("IsSelected")))
            {
                e.Cancel = true;
            }

            if (e.Column.Header.Equals("IsSelected"))
            {
                e.Column.IsReadOnly = false;
            }
        }

        private void ButtonDownload_OnClick(object sender, RoutedEventArgs e)
        {
            List<SelectableViewModel> list = new List<SelectableViewModel>();

            /*RefreshDataGridHelper.Refresh(
                (ObservableCollection<SelectableViewModel>) (DataGridFile.DataContext),
                "select * from fileInfo where ownedBy=@ownedBy order by fileName",
                new Object[1] {_userModel._account});*/
            for (int i = 0; i < DataGridFile.Items.Count; i++)
            {
                SelectableViewModel selectableViewModel = (SelectableViewModel) DataGridFile.Items[i];
                if (selectableViewModel.IsSelected && File.Exists(selectableViewModel.FilePath))
                {
                    list.Add(selectableViewModel);
                }
            }

            if (list.Count == 0)
                return;

            FolderBrowserDialog openFileDialog = new FolderBrowserDialog(); //选择文件夹
            string savePath;
            if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK
            ) //注意，此处一定要手动引入System.Window.Forms空间，否则你如果使用默认的DialogResult会发现没有OK属性
            {
                savePath = openFileDialog.SelectedPath;
            }
            else
                return;

            try
            {
                for (int i = 0; i < list.Count; i++)
                {
                    File.Copy(list[i].FilePath,
                        savePath + "\\download\\" + list[i].FileName, true);
                    if (!FileMD5Helper.GetMD5HashFromFile(savePath + "\\download\\" + list[i].FileName)
                        .Equals(list[i].FileMd5))
                    {
                        MessageTips("文件MD5校验失败，未完成 " + list[i].FileName + " 的下载。", sender, e,
                            "RootDialog");
                    }
                }

                MessageTips("下载成功！", sender, e, "RootDialog");
            }
            catch (Exception exception)
            {
                MessageTips("下载失败！" + exception.Message, sender, e, "RootDialog");
            }
        }

        public async void MessageTips(string message, object sender, RoutedEventArgs e, string identifier)
        {
            var sampleMessageDialog = new MessageDialog
            {
                Message = {Text = message}
            };
            await DialogHost.Show(sampleMessageDialog, identifier);
        }

        private void ButtonShare_OnClick(object sender, RoutedEventArgs e)
        {
            List<SelectableViewModel> list = new List<SelectableViewModel>();
            for (int i = 0; i < DataGridFile.Items.Count; i++)
            {
                SelectableViewModel selectableViewModel = (SelectableViewModel) DataGridFile.Items[i];
                if (selectableViewModel.IsSelected && File.Exists(selectableViewModel.FilePath))
                {
                    list.Add(selectableViewModel);
                }
            }

            if (list.Count != 1)
                return;
            Clipboard.SetDataObject(list[0].FileMd5+ _userModel._account);
            MessageTips("文件分享邀请码：\n“" + list[0].FileMd5 + _userModel._account + "”\n，已复制到剪贴板。", sender, e,
                "RootDialog");
        }

        private void ButtonShareDownload_OnClick(object sender, RoutedEventArgs e)
        {
        }
    }
}